package org.example.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.annotation.LeakyBucketLimit;
import org.example.exception.RateLimitException;
import org.example.model.LeakyBucket;
import org.example.properties.RateLimitProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author liyishan
 * @date 2024/1/10 15:59
 * @apiNote
 */
@Aspect
@Component
@Slf4j
public class LeakyBucketLimitAspect {
    @Autowired
    private RateLimitProperties limitProperties;


    @Pointcut("@annotation(org.example.annotation.LeakyBucketLimit)")
    protected void lockPoint(){
    }

    @Around("lockPoint()")
    public Object round(ProceedingJoinPoint point)throws Throwable{
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> clazz = method.getDeclaringClass();
        LeakyBucketLimit leakyBucketLimit;
        leakyBucketLimit = method.getAnnotation(LeakyBucketLimit.class);
        if (leakyBucketLimit == null) {
            leakyBucketLimit = clazz.getAnnotation(LeakyBucketLimit.class);
            if (leakyBucketLimit == null) {
                return point.proceed();
            }
        }

        // 根据用户IP（可选）和接口方法，构造key
        String key = limitProperties.getKey(leakyBucketLimit.value(), leakyBucketLimit.key(), clazz.getSimpleName(), method.getName(), limitProperties.getStrategyConfig().getLeakyBucketLimitStrategy());

        LeakyBucket leakyBucket = LeakyBucket.getLeakyBucket(key);
        if (leakyBucket == null) {
            LeakyBucket.putLeakyBucket(key, leakyBucketLimit.capacity(), leakyBucketLimit.leakRate());
            return point.proceed();
        }
        if (leakyBucket.isFull()) {
            throw new RateLimitException("请求过于频繁，请稍后重试！");
        }
        Object object = point.proceed();
        return object;
    }

}
